package cn.memedai.router.compiler;

import static cn.memedai.router.compiler.Consts.ACTIVITY_FULL_NAME;
import static cn.memedai.router.compiler.Consts.INTERCEPTOR_TABLE;
import static cn.memedai.router.compiler.Consts.INTERCEPTOR_TABLE_METHOD_NAME;
import static cn.memedai.router.compiler.Consts.PACKAGE_NAME;
import static cn.memedai.router.compiler.Consts.ROUTE_TABLE;
import static cn.memedai.router.compiler.Consts.ROUTE_TABLE_METHOD_NAME;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;

import cn.memedai.router.annotation.Module;
import cn.memedai.router.annotation.Modules;
import cn.memedai.router.annotation.Route;

public class RouteProcessor extends AbstractProcessor {
    private Elements elementUtils = null;
    private Filer filer = null;
    private Messager messager = null;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        filer = processingEnvironment.getFiler();
        messager = processingEnvironment.getMessager();
    }

    /**
     * This method will be called some times.
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        debug(null,"process apt with " + set.toString());
        if (set.isEmpty()) {
            return false;
        }
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Route.class);
        if (elements == null || elements.isEmpty()) {
            return true;
        }
        // 合法的TypeElement集合
        Set<TypeElement> typeElements = new HashSet<>();
        for (Element element : elements) {
            if (validateElement(element)) {
                typeElements.add((TypeElement) element);
            }
        }
        // module
        String moduleName = null;
        Set<? extends Element> moduleList = roundEnvironment.getElementsAnnotatedWith(Module.class);
        if (moduleList != null && moduleList.size() > 0) {
            Module annotation = moduleList.iterator().next().getAnnotation(Module.class);
            moduleName = annotation.value();
        }
        // modules
        String[] moduleNames = null;
        Set<? extends Element> modulesList = roundEnvironment.getElementsAnnotatedWith(
                Modules.class);
        if (modulesList != null && modulesList.size() > 0) {
            Element modules = modulesList.iterator().next();
            moduleNames = modules.getAnnotation(Modules.class).value();
        }
        if (moduleName != null) {
            generateRouteTable(moduleName, typeElements);
            generateInterceptorTable(moduleName, typeElements);
        } else {
            error(null, "No Module `%s` defined in annotation processor.", moduleName);
        }
        if (moduleNames !=null) {
            generateModulesRouterInit(moduleNames);
        }
        return true;
    }

    /**
     * Verify the annotated class.
     */
    private boolean validateElement(Element typeElement) {
        if (!processingEnv.getTypeUtils().isSubtype(typeElement.asType(),
                processingEnv.getElementUtils().getTypeElement(ACTIVITY_FULL_NAME).asType())) {
            error(typeElement, "%s is not a subclass of `%s`.",
                    typeElement.getSimpleName().toString(), ACTIVITY_FULL_NAME);
            return false;
        }
        Set<Modifier> modifiers = typeElement.getModifiers();
        // non-public class.
        if (!modifiers.contains(Modifier.PUBLIC)) {
            error(typeElement, "The class %s is not public.",
                    ((TypeElement) typeElement).getQualifiedName());
            return false;
        }
        // abstract class.
        if (modifiers.contains(Modifier.ABSTRACT)) {
            error(typeElement,
                    "The class %s is abstract. You can't annotate abstract classes with @%s.",
                    ((TypeElement) typeElement).getQualifiedName(), Route.class.getSimpleName());
            return false;
        }
        return true;
    }

    private void error(Element element, String message, Object... args) {
        messager.printMessage(Diagnostic.Kind.ERROR, String.format(message, args), element);
    }

    private void debug(Element element, String message, Object... args) {
        messager.printMessage(Diagnostic.Kind.WARNING, String.format(message, args), element);
    }

    /**
     * generate ModulesRouter init
     */
    private void generateModulesRouterInit(String[] moduleNames) {
        MethodSpec.Builder initMethod = MethodSpec.methodBuilder("init")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC);
        for (String module : moduleNames) {
            initMethod.addStatement(capitalize(module) + ROUTE_TABLE + ".handleActivityTable()");
            initMethod.addStatement(
                    capitalize(module) + INTERCEPTOR_TABLE + ".handleInterceptorTable()");
        }
        TypeSpec routerInit = TypeSpec.classBuilder("RouterInit")
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                .addMethod(initMethod.build())
                .build();
        try {
            JavaFile.builder("cn.memedai.router", routerInit)
                    .build()
                    .writeTo(filer);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * RouteTable.
     */
    private void generateRouteTable(String moduleName, Set<TypeElement> elements) {
        MethodSpec.Builder handleActivityTable = MethodSpec.methodBuilder(ROUTE_TABLE_METHOD_NAME)
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC);

        for (TypeElement element : elements) {
            Route route = element.getAnnotation(Route.class);
            String[] paths = route.value();
            for (String path : paths) {
                handleActivityTable.addStatement(
                        "cn.memedai.router.AptHub.sActivityTable.put($S, $T.class)", path,
                        ClassName.get(element));
            }
        }

        TypeSpec type = TypeSpec.classBuilder(capitalize(moduleName) + ROUTE_TABLE)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(handleActivityTable.build())
                .addJavadoc("Generated by Router. Do not edit it!\n")
                .build();
        try {
            JavaFile.builder(PACKAGE_NAME, type).build().writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * InterceptorTable.
     */
    private void generateInterceptorTable(String moduleName, Set<TypeElement> elements) {
        MethodSpec.Builder handleInterceptorTable = MethodSpec.methodBuilder(
                INTERCEPTOR_TABLE_METHOD_NAME)
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC);

        for (TypeElement element : elements) {
            Route route = element.getAnnotation(Route.class);
            String interceptors = route.interceptors();
            if (!interceptors.isEmpty()) {
                handleInterceptorTable.addStatement(
                        "cn.memedai.router.AptHub.sInterceptorTable.put($T.class, $S)",
                        ClassName.get(element), interceptors);
            }
        }
        TypeSpec type = TypeSpec.classBuilder(capitalize(moduleName) + INTERCEPTOR_TABLE)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(handleInterceptorTable.build())
                .addJavadoc("Generated by Router. Do not edit it!\n")
                .build();
        try {
            JavaFile.builder(PACKAGE_NAME, type).build().writeTo(filer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private String capitalize(CharSequence self) {
        return self.length() == 0 ? "" :
                "" + Character.toUpperCase(self.charAt(0)) + self.subSequence(1, self.length());
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> ret = new HashSet<>();
        ret.add(Modules.class.getCanonicalName());
        ret.add(Module.class.getCanonicalName());
        ret.add(Route.class.getCanonicalName());
        return ret;
    }
}